using System;
using System.Collections.Generic;
using JigLibX.Physics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using SolarWinds.Controllers;
using SolarWinds.Effects;
using SolarWinds.Logic;
using SolarWinds.Particles;
using SolarWinds.Widgets;
using SolarWinds.Cameras;
using SolarWinds.Shapes;
using SolarWinds.Renderers;

namespace SolarWinds.Screens
{
    /// <summary>
    /// A screen that can contain widgets and a 3d scene
    /// </summary>
    public class GameScreen : Screen
    {
        readonly WidgetManager mWidgetManager;

        Window mWindow;
        ScrollBar mPositionBar;
        ScrollBar mVolumeBar;
        CheckBox mCheckbox;
        PushButton mPlayButton;
        PushButton mPauseButton;

        private Label mLabel;
        private TextBox mTextBox;
        
        #region Constants

        /// <summary>
        /// The period of the parallax motion in the starfield.
        /// </summary>
        const float StarsParallaxPeriod = 30f;


        /// <summary>
        /// The amplitude of the parallax motion in the starfield.
        /// </summary>
        const float StarsParallaxAmplitude = 2048f;

        #endregion

        /// <summary>
        /// Persistent movement tracker, used to slightly parallax the stars.
        /// </summary>
        private double mMovement;

        private readonly Rectangle[] msrcRects = {
                                                     new Rectangle(0, 0, 256, 256),
                                                     new Rectangle(256, 0, 256, 256),
                                                     new Rectangle(512, 0, 256, 256)
                                                 };

        private static ShapeManager sShapeManager;

        private readonly Starfield2D mStartField;
        private AsteroidField mAsteroidField;
        private ShipShape mShip;
        private ShipController mShipController;
        private ShieldShape mBackRightShieldShape;
        private ShieldShape mBackLeftShieldShape;
        private ShieldShape mFrontLeftShieldShape;
        private ShieldShape mFrontRightShieldShape;
        private readonly List<ProjectileShape> mProjectiles = new List<ProjectileShape>();
        
        private StatisticsDisplay mStatsDisplay;
        
        private bool mGameOver = false;
        private int mKills;

        public static ShapeManager ShapeManager
        {
            get { return sShapeManager; }
        }
        
        public GameScreen(Game game)
            : base(game)
        {
            var camera = new Camera(game);
            Components.Add(camera);
            SolarWindsGame.Camera = camera;
            
            var cameraController = new CameraController(game) {Camera = camera};
            Components.Add(cameraController);

            mStartField = new Starfield2D(Game)
            {
                Position = Vector2.Multiply(new Vector2(
                                                (float)Math.Cos(mMovement / StarsParallaxPeriod),
                                                (float)Math.Sin(mMovement / StarsParallaxPeriod)),
                                            StarsParallaxAmplitude)
            };
            Components.Add(mStartField);

            if (sShapeManager == null)
            {
                sShapeManager = new ShapeManager(game); 
                Components.Add(sShapeManager);
            }

            // bounding box renderer
            var bboxRenderer = new BoundingBoxRenderer(Game);
            bboxRenderer.Components = sShapeManager.Components;
            //Components.Add(bboxRenderer);

            // bounding sphere renderer
            var bsRenderer = new BoundingSphereRenderer(Game);
            bsRenderer.Components = sShapeManager.Components;
            Components.Add(bsRenderer);

            // bounding sphere renderer
            var pdRenderer = new PhysicsDebugRenderer(Game);
            Components.Add(pdRenderer);
            
            // install key controller
            mWidgetManager = new WidgetManager(game);
            Components.Add(mWidgetManager);
        }

        public override void Initialize()
        {
            base.Initialize();

            Game.IsMouseVisible = true;

            #region Space ship

            // the space ship
            mShip = new ShipShape(Game);
            ShapeManager.Components.Add(mShip);
            
            mShipController = new ShipController(Game, mShip.Actor.Body);
            mShipController.PrimaryFire += mShipController_PrimaryFire;
            Components.Add(mShipController);
            
            // the asteroids
            mAsteroidField = new AsteroidField(Game);
            ShapeManager.Components.Add(mAsteroidField);

            // grid
            var gridShape = new GridShape(Game);
            gridShape.Scale = Vector3.One * 100.0f;
            ShapeManager.Components.Add(gridShape);

            // coordsystem
            var coordShape = new CoordSystemShape(Game);
            coordShape.Scale = Vector3.One * 100.0f;
            ShapeManager.Components.Add(coordShape);

            // shields
            mBackRightShieldShape = new ShieldShape(Game);
            mBackRightShieldShape.Scale = Vector3.One * 100.0f;
            mBackRightShieldShape.Rotation = Quaternion.CreateFromAxisAngle(Vector3.Up, MathHelper.ToRadians(45));
            ShapeManager.Components.Add(mBackRightShieldShape);

            mBackLeftShieldShape = new ShieldShape(Game);
            mBackLeftShieldShape.Scale = Vector3.One * 100.0f;
            mBackLeftShieldShape.Rotation = Quaternion.CreateFromAxisAngle(Vector3.Up, MathHelper.ToRadians(45-90));
            ShapeManager.Components.Add(mBackLeftShieldShape);

            mFrontLeftShieldShape = new ShieldShape(Game);
            mFrontLeftShieldShape.Scale = Vector3.One * 100.0f;
            mFrontLeftShieldShape.Rotation = Quaternion.CreateFromAxisAngle(Vector3.Up, MathHelper.ToRadians(45-180));
            ShapeManager.Components.Add(mFrontLeftShieldShape);

            mFrontRightShieldShape = new ShieldShape(Game);
            mFrontRightShieldShape.Scale = Vector3.One * 100.0f;
            mFrontRightShieldShape.Rotation = Quaternion.CreateFromAxisAngle(Vector3.Up, MathHelper.ToRadians(45-270));
            ShapeManager.Components.Add(mFrontRightShieldShape);

            #endregion

            #region Particles
            
            // Create an info class for an emitter
            var emitterDescription = new ParticleEmitterDescription();
            emitterDescription.EmissonRate = 200;
            emitterDescription.EmitForever = true;
            emitterDescription.EmitTime = 0.5f;
            emitterDescription.Direction = new Vector3(0.0f, 0.0f, -1.0f);
            emitterDescription.Spread = 15.0f; // in degree;
            emitterDescription.Speed = 2.0f;
            emitterDescription.LifeSpan = 2.0f;
            emitterDescription.Mass = 0.1f;
            emitterDescription.TextureAsset = "fractal";
            emitterDescription.ColorRampAsset = "fireRamp";
            emitterDescription.EffectAsset = "SimpleParticle";

            // Create an emitter and assign info to it
            var emitter = new OmniParticleEmitter(emitterDescription);
            
            // Create forces and add them to emitter
            var gravity = new ParticleForceGravity();
            emitter.Forces.Add(gravity);
            //mSceneManager.AddNode(emitterNode);
            //pm.AddEmitter(emitterNode);*/

            var emitterShape = new ParticleEmitterShape(Game);
            emitterShape.Emitter = emitter;
            Components.Add(emitterShape);

            #endregion

            #region Effects

            var lf = new LensFlare(Game, "lensflare");
            lf.LightPosition = new Vector3(3, 0, -900);

            // Small red/yellow/gray halo behind sun
            lf.AddSpot(new LensFlare.Spot(LensFlare.SpotType.Circle, 
                                          -0.2f, 0.0f, 0.55f, new Color(20, 175, 175, 255)));

            // The sun, sun+streaks+glow+red ring
            lf.AddSpot(new LensFlare.Spot(LensFlare.SpotType.Sun, 
                                          0.0f, 0.0f, 0.6f, new Color(255, 255, 255, 255)));
            lf.AddSpot(new LensFlare.Spot(LensFlare.SpotType.Streaks, 
                                          0.0f, 0.0f, 1.5f, new Color(128, 255, 255, 255)));
            lf.AddSpot(new LensFlare.Spot(LensFlare.SpotType.Glow, 
                                          0.0f, 0.0f, 1.7f, new Color(100, 255, 255, 200)));
            //new FlareData(
            //RingNum, 1.0f, 0.9f, new Color(150, 255, 120, 120)),

            // 3 blue circles at 0.5 distance
            lf.AddSpot(new LensFlare.Spot(LensFlare.SpotType.Circle, 
                                          0.5f, 0.0f, 0.12f, new Color(35, 60, 60, 180)));
            lf.AddSpot(new LensFlare.Spot(LensFlare.SpotType.Circle, 
                                          0.55f, 0.0f, 0.46f, new Color(60, 100, 100, 200)));
            lf.AddSpot(new LensFlare.Spot(LensFlare.SpotType.Circle, 
                                          0.6f, 0.0f, 0.17f, new Color(40, 120, 120, 220)));

            lf.AddSpot(new LensFlare.Spot(LensFlare.SpotType.Ring, 
                                          0.85f, 0.0f, 0.2f, new Color(100, 60, 60, 255)));
            lf.AddSpot(new LensFlare.Spot(LensFlare.SpotType.Flare, 
                                          1.5f, 0.0f, 0.2f, new Color(130, 255, 60, 60)));
            lf.AddSpot(new LensFlare.Spot(LensFlare.SpotType.Flare, 
                                          1.15f, 0.0f, 0.15f, new Color(90, 255, 60, 60)));
            lf.AddSpot(new LensFlare.Spot(LensFlare.SpotType.Halo, 
                                          1.3f, 0.0f, 0.6f, new Color(180, 60, 60, 255)));
			
            // 3 red halos and circles on the opposite side of the blue halos
            lf.AddSpot(new LensFlare.Spot(LensFlare.SpotType.Halo, 
                                          1.4f, 0.0f, 0.2f, new Color(98, 220, 80, 80)));
            //new FlareData(
            //HaloNum, -0.45f, 0.6f, new Color(95, 220, 80, 80)),
            lf.AddSpot(new LensFlare.Spot(LensFlare.SpotType.Circle, 
                                          1.5f, 0.0f, 0.1f, new Color(85, 220, 80, 80)));

            lf.AddSpot(new LensFlare.Spot(LensFlare.SpotType.Halo,
                                          1.6f, 0.0f, 0.5f, new Color(80, 60, 60, 255)));
            lf.AddSpot(new LensFlare.Spot(LensFlare.SpotType.Ring, 
                                          1.8f, 0.0f, 0.3f, new Color(110, 90, 60, 255)));

            lf.AddSpot(new LensFlare.Spot(LensFlare.SpotType.Halo, 
                                          1.95f, 0.0f, 0.5f, new Color(120, 60, 60, 255)));
            lf.AddSpot(new LensFlare.Spot(LensFlare.SpotType.Circle, 
                                          2.0f, 0.0f, 0.15f, new Color(85, 60, 60, 255)));
            lf.DrawOrder = 2;
            Components.Add(lf);

            #endregion
            
            #region Skybox
            
            var sb = new SkyBox(Game)
                         {
                             AssetBack = "stars",
                             AssetLeft = "stars",
                             AssetRight = "stars",
                             AssetFront = "stars",
                             AssetBottom = "stars",
                             AssetTop = "stars",
                             DrawOrder = 1
                         };
            //Components.Add(sb);
            
            #endregion

            #region Widgets

            var fpsDisplay = new FPSDisplay(Game) {Position = new Point(20, 20), DrawOrder = 100};
            mWidgetManager.AddChild(fpsDisplay);

            mStatsDisplay = new StatisticsDisplay(Game) { Position = new Point(GraphicsDevice.Viewport.Width - 220, 20), DrawOrder = 100 };
            mWidgetManager.AddChild(mStatsDisplay);
            mStatsDisplay.Statistics.Add("Asteroids", 0);

            /*
            mWindow = new Window(Game)
                          {
                              Position = new Point(100, 200),
                              Caption = "MP3 Player Widget",
                              Width = 300,
                              Height = 300
                          };
            mWidgetManager.AddChild(mWindow);
            //mWindow.Hide();

            mTextBox = new TextBox(Game) {Width = 100, Height = 16};

            var sp = new Spectrum(Game) {Position = new Point(200, 20), Width = 100, Height = 50};

            mLabel = new Label(Game) {Width = 100, Height = 50};

            mPlayButton = new PushButton(Game)
                              {
                                  Position = new Point(10, 20),
                                  Width = 100,
                                  Height = 16,
                                  Caption = "Play"
                              };

            mPauseButton = new PushButton(Game)
                               {
                                   Position = new Point(120, 20),
                                   Width = 100,
                                   Height = 16,
                                   Caption = "Pause"
                               };

            var exitButton = new PushButton(Game)
                                 {
                                     Position = new Point(230, 20),
                                     Width = 100,
                                     Height = 16,
                                     Caption = "Exit"
                                 };

            mCheckbox = new CheckBox(Game);
            mCheckbox.Position = new Point(10, 50);
            mCheckbox.Width = 300;
            mCheckbox.Height = 16;
            mCheckbox.Caption = "Use Effects";
            
            mPositionBar = new ScrollBar(Game);
            mPositionBar.Position = new Point(10, 130);
            mPositionBar.Width = 200;
            mPositionBar.Height = 16;
            mPositionBar.Orientation = Orientation.Horizontal;

            mVolumeBar = new ScrollBar(Game);
            mVolumeBar.Position = new Point(10, 150);
            mVolumeBar.Width = 200;
            mVolumeBar.Height = 16;
            mVolumeBar.Orientation = Orientation.Horizontal;

            mWindow.Layout = new BoxLayout(Direction.TopToBottom, mWindow);
            mWindow.Layout.Spacing = 20;
            mWindow.AddChild(sp);
            mWindow.AddChild(mLabel);
            mWindow.AddChild(mTextBox);
            mWindow.AddChild(mPlayButton);
            mWindow.AddChild(mPauseButton);
            mWindow.AddChild(exitButton);
            mWindow.AddChild(mCheckbox);
            mWindow.AddChild(mPositionBar);
            mWindow.AddChild(mVolumeBar);

            mPositionBar.Value = 0.0f;
            mPositionBar.Pressed += BarPressed;
            mPositionBar.Released += BarReleased;

            mVolumeBar.Value = 0.0f;
            mVolumeBar.MaxValue = 1.0f;
            mVolumeBar.ValueChanged += VolumeBarValueChanged;

            exitButton.Clicked += button4_Clicked;
            mPlayButton.Clicked += play_Clicked;
            mPauseButton.Clicked += pause_Clicked;
                        
            FPSDisplay fpsDisplay = new FPSDisplay(Game);
            fpsDisplay.Position = new Point(20, 20);
            mWidgetManager.AddWidget(fpsDisplay);
           
            Window w = new Window(Game);
            w.Position = new Point(50, 120);
            w.Caption = "Widget";
            w.Width = 100;
            w.Height = 100;
            mWidgetManager.AddChild(w);

            w.Layout = new BoxLayout(Direction.LeftToRight, w);
            w.AddChild(radioButton1);
            w.AddChild(radioButton2);
            w.AddChild(radioButton3);
            
            RadioButton radioButton1 = new RadioButton(Game);
            radioButton1.Position = new Point(10, 20);
            radioButton1.Width = 300;
            radioButton1.Height = 16;
            radioButton1.Caption = "First Radio";

            RadioButton radioButton2 = new RadioButton(Game);
            radioButton2.Position = new Point(10, 40);
            radioButton2.Width = 300;
            radioButton2.Height = 16;
            radioButton2.Caption = "Second Radio";
            
            RadioButton radioButton3 = new RadioButton(Game);
            radioButton3.Position = new Point(10, 60);
            radioButton3.Width = 300;
            radioButton3.Height = 16;
            radioButton3.Caption = "Third Radio";
            

            mWidgetManager.DrawOrder = 99;*/

            #endregion
        }

        void mShipController_PrimaryFire(object sender, EventArgs e)
        {
            var projectile = new ProjectileShape(Game)
            {
                Direction = Vector3.UnitZ * 500,
                Position = mShip.Actor.BodyPosition + Vector3.UnitZ * 10,
                Rotation = Quaternion.CreateFromAxisAngle(Vector3.UnitX, MathHelper.ToRadians(180)),
                Scale = Vector3.One * 0.12f
            };
            ShapeManager.Components.Add(projectile);
            mProjectiles.Add(projectile);
        }

        public override void Update(GameTime gameTime)
        {
            // FIXME Quick and dirty
            if (mGameOver) return;

            base.Update(gameTime);

            // updating the physics system
            float timeStep = (float)gameTime.ElapsedGameTime.Ticks / TimeSpan.TicksPerSecond;
            PhysicsSystem.CurrentPhysicsSystem.Integrate(timeStep);

            // update the parallax movement
            mMovement += gameTime.ElapsedRealTime.TotalSeconds;
            Vector2 position = Vector2.Multiply(new Vector2(
                                                    (float)Math.Cos(-mMovement / StarsParallaxPeriod),
                                                    (float)Math.Sin(-mMovement / StarsParallaxPeriod)),
                                                StarsParallaxAmplitude);

            // update the stars
            mStartField.Position = position;

            // update stats
            mStatsDisplay.Statistics["Asteroids"] = mAsteroidField.Components.Count;
            mStatsDisplay.Statistics["Kills"] = mKills;

            // update shields
            Vector3 pos = mShip.Position;
            mBackLeftShieldShape.Position = pos;
            mBackRightShieldShape.Position = pos;
            mFrontLeftShieldShape.Position = pos;
            mFrontRightShieldShape.Position = pos;

            if (mBackLeftShieldShape.Killed || mBackRightShieldShape.Killed ||
                mFrontLeftShieldShape.Killed || mFrontRightShieldShape.Killed)
            {
                mWindow = new Window(Game)
                {
                    Position = new Point(100, 200),
                    Caption = "Game Over",
                    Width = 300,
                    Height = 300
                };
                mWidgetManager.AddChild(mWindow);

                mGameOver = true;
            }
            else
            {
                mBackLeftShieldShape.Level -= mShip.mBackLeftShieldDamage;
                mBackRightShieldShape.Level -= mShip.mBackRightShieldDamage;
                mFrontLeftShieldShape.Level -= mShip.mFrontLeftShieldDamage;
                mFrontRightShieldShape.Level -= mShip.mFrontRightShieldDamage;

                mShip.mBackLeftShieldDamage = 0;
                mShip.mBackRightShieldDamage = 0;
                mShip.mFrontLeftShieldDamage = 0;
                mShip.mFrontRightShieldDamage = 0;    
            }

            // remove projectiles
            var toBeRemoved = new List<ProjectileShape>();
            foreach (ProjectileShape projectile in mProjectiles)
            {
                if (projectile.LifeTime < 0.0f)
                {
                    toBeRemoved.Add(projectile);
                }

                if (projectile.Killed)
                {
                    // we hit an asteroid
                    toBeRemoved.Add(projectile);

                    mKills += 1;
                }
            }

            foreach (ProjectileShape projectile in toBeRemoved)
            {
                GameScreen.ShapeManager.Components.Remove(projectile);
                mProjectiles.Remove(projectile);
                projectile.Dispose();
            }
        }
    }
}